﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Web;
using System.Web.Mvc;
using System.Data.Services.Client;
using Microsoft.Samples.ServiceHosting.StorageClient;

namespace AzureStorageManagerMVC.Controllers
{
	[HandleError]
	public class HomeController : Controller
	{
		public class TestTableEntity : TableStorageEntity
		{
			public TestTableEntity(string partitionKey, string rowKey) : base(partitionKey, rowKey)
			{
			}

			public TestTableEntity() : base()
			{
				PartitionKey = Guid.NewGuid().ToString();
				RowKey = String.Empty;
			}
			
			public string FirstName { get; set; }
			public string LastName { get; set; }
			public string EmployeeId { get; set; }
		}

		internal class TestDataServiceContext : TableStorageDataServiceContext
		{
			internal TestDataServiceContext(StorageAccountInfo accountInfo)
				: base(accountInfo)
			{
			}

			internal const string TestTableName = "TestTable";

			public IQueryable<TestTableEntity> TestTable
			{
				get
				{
					return this.CreateQuery<TestTableEntity>(TestTableName);
				}
			}
		}

		class GenericEntity : TableStorageEntity
		{
			public GenericEntity(string partitionKey, string rowKey) : base(partitionKey, rowKey)
			{
				Properties = new Dictionary<string, string>();
			}

			public GenericEntity() : base()
			{
				PartitionKey = Guid.NewGuid().ToString();
				RowKey = String.Empty;

				Properties = new Dictionary<string, string>();
			}

			public void Add(string name, string value)
			{
				Properties.Add(name, value);
			}
			
			public void AddWithFilter(string name, string value)
			{
				if (name.CompareTo("PartitionKey") == 0)
					PartitionKey = value;
				else if (name.CompareTo("RowKey") == 0)
					RowKey = value;
				else if (name.CompareTo("Timestamp") != 0)
					Properties.Add(name, value);
			}

			public Dictionary<string, string> Properties { get; private set; }
		};

		public ActionResult Index()
		{
			return View();
		}

		protected StorageAccountInfo GetBlobAccountInfo()
		{
			bool use_dev_account = (Session["UseDevAccount"] as Nullable<bool>).GetValueOrDefault(true);
			return new StorageAccountInfo(new Uri(Session["BlobStorageAddr"] as string), use_dev_account, Session["AccountName"] as string, Session["AccountKey"] as string);
		}
		
		protected StorageAccountInfo GetTableAccountInfo()
		{
			bool use_dev_account = (Session["UseDevAccount"] as Nullable<bool>).GetValueOrDefault(true);
			return new StorageAccountInfo(new Uri(Session["TableStorageAddr"] as string), use_dev_account, Session["AccountName"] as string, Session["AccountKey"] as string);
		}
		
		protected StorageAccountInfo GetQueueAccountInfo()
		{
			bool use_dev_account = (Session["UseDevAccount"] as Nullable<bool>).GetValueOrDefault(true);
			return new StorageAccountInfo(new Uri(Session["QueueStorageAddr"] as string), use_dev_account, Session["AccountName"] as string, Session["AccountKey"] as string);
		}

		public ActionResult SetAccount(bool use_development, string account_name, string account_key, string storage_addr)
		{
			if( use_development )
			{
				Session["UseDevAccount"] = new Nullable<bool>(true);
				Session["AccountName"] = "devstoreaccount1";
				Session["AccountKey"] = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
				Session["BlobStorageAddr"] = ((null == storage_addr || storage_addr.Length == 0) ? "http://127.0.0.1:10000" : (storage_addr + ":9000"));
				Session["QueueStorageAddr"] = ((null == storage_addr || storage_addr.Length == 0) ? "http://127.0.0.1:10001" : (storage_addr + ":9001"));
				Session["TableStorageAddr"] = ((null == storage_addr || storage_addr.Length == 0) ? "http://127.0.0.1:10002" : (storage_addr + ":9002"));
			}
			else
			{
				Session["UseDevAccount"] = new Nullable<bool>(false);
				Session["AccountName"] = account_name;
				Session["AccountKey"] = account_key;
				Session["BlobStorageAddr"] = "https://blob.core.windows.net";
				Session["QueueStorageAddr"] = "https://queue.core.windows.net";
				Session["TableStorageAddr"] = "https://table.core.windows.net";
			}

			return Json(new { rc = true, text = "" }, "text/html");
		}

		public ActionResult ListBlobContainers()
		{
			try
			{
				var accountInfo = GetBlobAccountInfo();

				if (null == accountInfo)
					return Json(new { blob_containers = new List<int>(), total = 0 });

				var blobStorage = BlobStorage.Create(accountInfo);

				var blobContainers = blobStorage.ListBlobContainers().Select(c => new { name = c.ContainerName, uri = c.ContainerUri, modified = c.LastModifiedTime.ToString("yyyy-MM-dd HH:mm:ss"), ispublic = c.GetContainerAccessControl() == ContainerAccessControl.Public }).ToList();

				return Json(new { blob_containers = blobContainers, total = blobContainers.Count });
			}
			catch(Exception)
			{
				return Json(new { blob_containers = new List<int>(), total = 0 });
			}
		}

		public ActionResult CreateBlobContainer(string name, string isPublic)
		{
			try
			{
				var blobStorage = BlobStorage.Create(GetBlobAccountInfo());

				ContainerAccessControl accessControl = (isPublic.CompareTo("true") == 0) ? ContainerAccessControl.Public : ContainerAccessControl.Private;

				blobStorage.GetBlobContainer(name).CreateContainer(null, accessControl);
			}
			catch(Exception ex)
			{
				return Json(new { rc = false, text = ex.Message }, "text/html");
			}

			return Json(new { rc = true, text = "" }, "text/html");
		}
		
		public ActionResult DeleteBlobContainer(string name)
		{
			try
			{
				var blobStorage = BlobStorage.Create(GetBlobAccountInfo());

				blobStorage.RetryPolicy = RetryPolicies.RetryN(3, TimeSpan.FromMilliseconds(500));

				blobStorage.GetBlobContainer(name).DeleteContainer();
			}
			catch (Exception ex)
			{
				return Json(new { rc = false, text = ex.Message }, "text/html");
			}

			return Json(new { rc = true, text = "" }, "text/html");
		}
		
		public class BlobInfo
		{
			public BlobInfo()
			{
			}

			public string name { get; set; }
			public string uri { get; set; }
			public string modified { get; set; }
			public string content_type { get; set; }
			public long content_length { get; set; }
			public string content_lang { get; set; }
			public string content_encoding { get; set; }
		}

		static BlobInfo ExtractBlobInfo(object blob)
		{
			BlobProperties props = blob as BlobProperties;

			if( null != props )
				return new BlobInfo() { 
					name = props.Name, 
					uri = props.Uri.AbsoluteUri, 
					modified = props.LastModifiedTime.ToString("yyyy-MM-dd HH:mm:ss"),
					content_type = props.ContentType,
					content_length = props.ContentLength,
					content_lang = props.ContentLanguage,
					content_encoding = props.ContentEncoding
				};

			return new BlobInfo() { name = blob as string };
		}

		public ActionResult ListBlobs(string container)
		{
			if( null == container )
				return Json(new { blobs = new List<BlobInfo>(), total = 0 });

			var accountInfo = GetBlobAccountInfo();

			if (null == accountInfo)
				return Json(new { blobs = new List<int>(), total = 0 });

			var blobStorage = BlobStorage.Create(accountInfo);

			var blobsInfo = blobStorage.GetBlobContainer(container).ListBlobs(string.Empty, false).Select(b => ExtractBlobInfo(b)).ToList();

			return Json(new { blobs = blobsInfo, total = blobsInfo.Count });
		}

		public ActionResult CreateBlob(string container)
		{
			if (null == container)
				return Json(new { success = false, error = "BLOB container must be specified" }, "text/html");

			try
			{
				var blobStorage = BlobStorage.Create(GetBlobAccountInfo());

				foreach (string file in Request.Files)
				{
					HttpPostedFileBase hpf = Request.Files[file] as HttpPostedFileBase;

					var blobName = System.IO.Path.GetFileName(hpf.FileName);

					var properties = new BlobProperties(blobName) { ContentType = hpf.ContentType };

					var blobContent = new BlobContents(hpf.InputStream);

					blobStorage.GetBlobContainer(container).CreateBlob(properties, blobContent, true);
				}
			}
			catch (Exception ex)
			{
				return Json(new { success = false, error = ex.Message }, "text/html"); 
			}

			return Json(new { success = true, error = "Stored" }, "text/html");
		}
		
		public ActionResult DeleteBlob(string container, string name)
		{
			try
			{
				var blobStorage = BlobStorage.Create(GetBlobAccountInfo());

				blobStorage.RetryPolicy = RetryPolicies.RetryN(3, TimeSpan.FromMilliseconds(500));

				blobStorage.GetBlobContainer(container).DeleteBlob(name);
			}
			catch (Exception ex)
			{
				return Json(new { rc = false, text = ex.Message }, "text/html");
			}

			return Json(new { rc = true, text = "" }, "text/html");
		}

		public ActionResult ListTables()
		{
			try
			{
				var accountInfo = GetTableAccountInfo();

				if (null == accountInfo)
					return Json(new { tables = new List<int>(), total = 0 });

				var tableStorage = TableStorage.Create(accountInfo);

				var tables = tableStorage.ListTables().Select(t => new { name = t } ).ToList();

				return Json(new { tables = tables, total = tables.Count });
			}
			catch(Exception)
			{
				return Json(new { tables = new List<int>(), total = 0 });
			}
		}

		public ActionResult CreateTable(string name)
		{
			try
			{
				var tableStorage = TableStorage.Create(GetTableAccountInfo());

				tableStorage.CreateTable(name);
			}
			catch(Exception ex)
			{
				return Json(new { rc = false, text = ex.Message }, "text/html");
			}

			return Json(new { rc = true, text = "" }, "text/html");
		}

		public ActionResult DeleteTable(string name)
		{
			try
			{
				var tableStorage = TableStorage.Create(GetTableAccountInfo());

				tableStorage.RetryPolicy = RetryPolicies.RetryN(3, TimeSpan.FromMilliseconds(500));

				tableStorage.DeleteTable(name);
			}
			catch (Exception ex)
			{
				return Json(new { rc = false, text = ex.Message }, "text/html");
			}

			return Json(new { rc = true, text = "" }, "text/html");
		}

		private void ReadingEntity(object sender, ReadingWritingEntityEventArgs e)
		{
			var entity = e.Entity as GenericEntity;

			string ns = e.Data.GetDefaultNamespace().NamespaceName;
			string nsm = e.Data.GetNamespaceOfPrefix("m").NamespaceName;

			var content = e.Data.Descendants(XName.Get("content", ns));
			var properties = content.Descendants(XName.Get("properties", nsm)).Descendants();

			foreach(var p in properties)
				entity.Add(p.Name.LocalName, p.Value);
		}

		public ActionResult ListTableEntities(string table)
		{
			try
			{
				var accountInfo = GetTableAccountInfo();

				if (null == accountInfo)
				{
					return Json(
						new
						{
							metaData = new
							{
								totalProperty = "total",
								root = "entities",
								fields = new List<int>(),
							},
							columns = new List<int>(),
							success = true,
							entities = new List<int>(),
							total = 0
						});
				}

				var tableStorage = TableStorage.Create(accountInfo);

				var context = tableStorage.GetDataServiceContext();
				
				context.ReadingEntity += new EventHandler<ReadingWritingEntityEventArgs>(ReadingEntity);

				var entities = context.CreateQuery<GenericEntity>(table);

				var query = new TableStorageDataServiceQuery<GenericEntity>(entities as DataServiceQuery<GenericEntity>);
				
				//var result = query.Execute().Select(e => new {partition_key = e.PartitionKey, row_key = e.RowKey, ts = e.Timestamp}).ToList();
				var result = query.Execute().Select(e => e.Properties).ToList();

				// build list of unique columns
				var unique_columns = new HashSet<string>();
				var columns = new List<string>();

				foreach(var e in result)
				{
					foreach(var p in e)
					{
						if( !unique_columns.Contains(p.Key) )
						{
							unique_columns.Add(p.Key);
							columns.Add(p.Key);
						}
					}
				}

				return Json(
					new { 
						metaData = new { 
							totalProperty = "total", root = "entities", fields = columns.Select(c => new { name = c } ),
						}, 
						columns = columns.Select(c => new { header = c, dataIndex = c }),
						success = true,
						entities = result, 
						total = result.Count
					});
			}
			catch(Exception ex)
			{
				return Json(new { entities = new List<int>(), total = 0, text = ex.Message });
			}
		}

		private void WritingEntity(object sender, ReadingWritingEntityEventArgs e)
		{
			var entity = e.Entity as GenericEntity;

			string ns = e.Data.GetDefaultNamespace().NamespaceName;
			string nsm = e.Data.GetNamespaceOfPrefix("m").NamespaceName;

			var content = e.Data.Descendants(XName.Get("content", ns));
			var properties = content.Descendants(XName.Get("properties", nsm)).FirstOrDefault();

			string nsd = properties.GetNamespaceOfPrefix("d").NamespaceName;

			foreach(var item in entity.Properties)
				properties.Add(new XElement(XName.Get(item.Key, nsd), item.Value));
		}

		public ActionResult CreateTableEntity(string __table_name__)
		{
			try
			{
				var tableStorage = TableStorage.Create(GetTableAccountInfo());

				var context = tableStorage.GetDataServiceContext();

				context.WritingEntity += new EventHandler<ReadingWritingEntityEventArgs>(WritingEntity);

				var entity = new GenericEntity();

				foreach(string key in Request.Form.Keys)
				{
					// add generic property, filtering out keys and erasing timestamp alltogether
					if (key.CompareTo("__table_name__") != 0)
						entity.AddWithFilter(key, Request.Form[key]);
				}

				context.AddObject(__table_name__, entity);

				context.SaveChanges();
			}
			catch (Exception ex)
			{
				return Json(new { rc = false, text = ex.Message }, "text/html");
			}

			return Json(new { rc = true, text = "" }, "text/html");
		}

		public ActionResult DeleteTableEntity(string table, string partition_key, string row_key)
		{
			try
			{
				var tableStorage = TableStorage.Create(GetTableAccountInfo());

				var context = tableStorage.GetDataServiceContext();

				var entity = new GenericEntity(partition_key, row_key);

				context.AttachTo(table, entity, "*");

				context.DeleteObject(entity);

				context.SaveChanges();
			}
			catch (Exception ex)
			{
				return Json(new { rc = false, text = ex.Message }, "text/html");
			}

			return Json(new { rc = true, text = "" }, "text/html");
		}

		public ActionResult ListQueues()
		{
			try
			{
				var accountInfo = GetQueueAccountInfo();

				if (null == accountInfo)
					return Json(new { queues = new List<int>(), total = 0 });

				var queueStorage = QueueStorage.Create(accountInfo);

				var queues = queueStorage.ListQueues().Select(q => new { name = q.Name, uri = q.QueueUri, approximate_count = q.ApproximateCount()}).ToList();

				return Json(new { queues = queues, total = queues.Count });
			}
			catch (Exception)
			{
				return Json(new { blob_containers = new List<int>(), total = 0 });
			}
		}

		public ActionResult CreateQueue(string name)
		{
			try
			{
				var queueStorage = QueueStorage.Create(GetQueueAccountInfo());

				MessageQueue queue = queueStorage.GetQueue(name);

				queue.CreateQueue();
			}
			catch (Exception ex)
			{
				return Json(new { rc = false, text = ex.Message }, "text/html");
			}

			return Json(new { rc = true, text = "" }, "text/html");
		}

		public ActionResult DeleteQueue(string name)
		{
			try
			{
				var queueStorage = QueueStorage.Create(GetQueueAccountInfo());

				queueStorage.RetryPolicy = RetryPolicies.RetryN(3, TimeSpan.FromMilliseconds(500));

				queueStorage.GetQueue(name).DeleteQueue();
			}
			catch (Exception ex)
			{
				return Json(new { rc = false, text = ex.Message }, "text/html");
			}

			return Json(new { rc = true, text = "" }, "text/html");
		}

		public ActionResult ListTopQueueMessages(string name)
		{
			var accountInfo = GetQueueAccountInfo();

			if (null == accountInfo)
				return Json(new { messages = new List<int>(), total = 0 });

			var queueStorage = QueueStorage.Create(accountInfo);

			var queue = queueStorage.GetQueue(name);

			var messages = queue.PeekMessages(32);

			if( null == messages )
				return Json(new { messages = new List<int>(), total = 0 });

			var selectedMessages = messages.Select(m => new { id = m.Id, created = m.InsertionTime.ToString("yyyy-MM-dd HH:mm:ss"), expires = m.ExpirationTime.ToString("yyyy-MM-dd HH:mm:ss"), content = m.ContentAsString() }).ToList();

			return Json(new { messages = selectedMessages, total = selectedMessages.Count });
		}

		public ActionResult PutQueueMessage(string queue, string content)
		{
			try
			{
				var queueStorage = QueueStorage.Create(GetQueueAccountInfo());

				queueStorage.GetQueue(queue).PutMessage(new Message(content));
			}
			catch (Exception ex)
			{
				return Json(new { rc = false, text = ex.Message });
			}

			return Json(new { rc = true, text = "" });
		}

		public ActionResult CreateBinaryMessage(string queue)
		{
			if (null == queue)
				return Json(new { success = false, error = "Queue must be specified" }, "text/html");

			try
			{
				var queueStorage = QueueStorage.Create(GetQueueAccountInfo());

				foreach (string file in Request.Files)
				{
					HttpPostedFileBase hpf = Request.Files[file] as HttpPostedFileBase;

					byte[] content = new byte[hpf.InputStream.Length];

					hpf.InputStream.Read(content, 0, (int)hpf.InputStream.Length);

					queueStorage.GetQueue(queue).PutMessage(new Message(content));
				}
			}
			catch (Exception ex)
			{
				return Json(new { success = false, error = ex.Message }, "text/html"); 
			}

			return Json(new { success = true, error = "Stored" }, "text/html");
		}

		public ActionResult GetQueueMessage(string queue, string content)
		{
			try
			{
				var queueStorage = QueueStorage.Create(GetQueueAccountInfo());

				var q = queueStorage.GetQueue(queue);
				var m = q.GetMessage();
				
				if( null == m )
					return Json(new { rc = false, text = "" });

				q.DeleteMessage(m);

				return Json(new { rc = true, text = m.ContentAsString() });
			}
			catch (Exception ex)
			{
				return Json(new { rc = false, text = ex.Message });
			}
		}
	}
}
